package com.cn.gmall.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.cn.gmall.common.result.Result;
import com.cn.gmall.common.result.ResultCodeEnum;
import com.cn.gmall.common.util.IpUtil;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;

import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;



import java.nio.charset.StandardCharsets;


@Component
public class AuthGlobalFilter implements GlobalFilter {

    @Autowired
    private RedisTemplate redisTemplate;
    //    路径匹配工具 按照Ant(* )的风格来
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    //    白名单
    @Value("${authUrls.url}")
    private String whiteUrl;

    /**
     * @param exchange ServerWebExchange是一个HTTP请求-响应交互的契约。提供对HTTP请求和响应的访问，并公开额外的服务器端处理相关属性和特性，如请求属性
     *                 服务网络交换器，存放着重要的请求-响应属性、请求实例和响应实例等等，有点像Context的角色
     * @param chain    网关过滤链表接口
     *                 用于过滤器的链式调用
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//        获取请求对象
        ServerHttpRequest request = exchange.getRequest();
//        获取响应对象
        ServerHttpResponse response = exchange.getResponse();
//----------------------------------------------
//        RequestPath path = request.getPath();    形如 ===/api/users
//        String string = request.getPath().toString();
//        String path1 = request.getURI().getPath();  形如 == http://example.com/api/users
//          获取到请求对象的路径 /api/*/*/*/*
        String path = request.getURI().getPath();
//        获取userId
        String userId = this.getUserId(request);


        /**
         * 内部接口,经过网关直接拦截  不允许外部访问
         */
        if (antPathMatcher.match("/**/inner/**", path)) {
            return backFilter(response, ResultCodeEnum.PERMISSION);
        }
        /***
         *  检验用户是否登录
         */
        if ("-1".equals(userId)) {
            return backFilter(response, ResultCodeEnum.ILLEGAL_REQUEST);
        }

        if (antPathMatcher.match("/**/auth/**", path) && StringUtils.isEmpty(userId)) {

            return backFilter(response, ResultCodeEnum.LOGIN_AUTH);
        }

//        白名单
        if (!StringUtils.isEmpty(whiteUrl)) {
            String[] splits = whiteUrl.split(",");
            for (String split : splits) {
/**
 *split遍历的每一个白名单url
 *
 * s1.indexOf(s2)
 * s1 中包含s2 返回开始匹配的下标 不包含为1
 */
                if (path.indexOf(split) != -1 && StringUtils.isEmpty(userId)) {
//              是白名单的地址 且 每有登录
//             -------------- 重定向-------设置状态码--303-----
                    response.setStatusCode(HttpStatus.SEE_OTHER);

//             设置 location头
                    response.getHeaders().set(HttpHeaders.LOCATION, "http://www.gmall.com/login.html?originUrl="
                            + request.getURI());
//                  响应
                    return response.setComplete();
                }
            }
        }

//    --------------------临时id---userTempId--- 放在请求头里面 携带给后面的请求-----------

        String userTempId = this.getUserTempId(request);

//        -----------携帶userId-----------------------
        if (!StringUtils.isEmpty(userId) || !StringUtils.isEmpty(userTempId)) {
            if (!StringUtils.isEmpty(userId)) {
                /***     request.mutate().header("userId",userId).build();解析
                 * request: 这是一个请求对象，用于构建 API 请求。
                 * mutate(): 这是一个方法或函数，用于执行特定的操作或操作串联。
                 * 在这种情况下，它可能表示对于给定的 API 请求，执行一个更改或变异的操作。
                 * header("userId", userId): 这是一个设置请求头的方法或函数。
                 * "userId"是请求头名称，userId是对应的值。请求头通常用于在HTTP请求中传递附加的元数据或标识信息。
                 * build(): 这是一个方法或函数，用于构建最终的请求对象
                 * ，并准备发送到服务器。在这里，它可能表示将之前设置的参数组装成最终的请求。
                 * 请注意，上述信息
                 */
                request.mutate().header("userId", userId).build();
            }
            if (!StringUtils.isEmpty(userTempId)) {
                request.mutate().header("userTempId", userTempId).build();
            }
            //       放行带参数
            return chain.filter(exchange.mutate().request(request).build());

        }
//放行 不带参数
        return chain.filter(exchange);
    }

    /**
     * 获取临时id
     *
     * @param request
     * @return
     */
    private String getUserTempId(ServerHttpRequest request) {
        String userTempId = request.getHeaders().getFirst("userTempId");
        if (StringUtils.isEmpty(userTempId)) {
            MultiValueMap<String, HttpCookie> cookies = request.getCookies();
            if (cookies != null) {
                HttpCookie cookie = cookies.getFirst("userTempId");
                if (cookie != null) {
                    userTempId = cookie.getValue();
                }
            }
        }
        return userTempId;
    }

    /***
     * 从request中获取 userId
     * @param request
     *
     *
     * @return
     */
    private String getUserId(ServerHttpRequest request) {
/**
 *          从请求头中 获取token
 *           token中存在的位置 (请求头 / cookie)
 *
 */
/*        List<String> list = request.getHeaders().get("token");
        String s = list.get(0);*/
//        先从header里面去取
        String token = request.getHeaders().getFirst("token");;
        if (StringUtils.isEmpty(token)) {
//            request请求头中的token为空 或没有 去cookie里面找
        MultiValueMap<String, HttpCookie> cookies = request.getCookies();
//           判断cookie里面能不能拿到东西
        if (!CollectionUtils.isEmpty(cookies)) {
            HttpCookie cookie = cookies.getFirst("token");
            if (cookie != null) {
                token = cookie.getValue();
            }
        }
        }

//        拿到token
        String userId = "";
        if (!StringUtils.isEmpty(token)) {
//          去redis里面查
            String str =(String)redisTemplate.opsForValue().get("user:login:" + token);;
            if (!StringUtils.isEmpty(str)) {
                JSONObject jsonObject = JSONObject.parseObject(str);

//          本次请求和redis的ip值 比较
                String ipAddress = IpUtil.getGatwayIpAddress(request);
                if (ipAddress.equals(jsonObject.getString("ip"))) {
                    userId = jsonObject.getString("userId");
                } else {
//                    表示这个请求不是当前用户发送的 (疑似信息被截取)
                    userId = "-1";
                }

            }

        }

        return userId;
    }

    /***
     *
     * @param response      响应
     * @param permission 状态码
     * @return
     */
    private Mono<Void> backFilter(ServerHttpResponse response, ResultCodeEnum permission) {
//返回会用户没有权限
        Result<Object> result = Result.build(null, permission);
//转成字节码(规则utf-8)
        byte[] bytes = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        /***
         * 创建缓冲区对象
         * 调用 bufferFactory() 方法返回一个 DataBufferFactory 对象，
         * 用于创建和操作缓冲区。
         * wrap(bytes)：在 DataBufferFactory 对象上调用 wrap(bytes) 方法，
         * 使用提供的字节数组 bytes 创建一个新的缓冲区对象。
         * 这个方法将字节数组包装成一个可以进行读取和写入操作的缓冲区
         *
         */
        DataBuffer wrap = response.bufferFactory().wrap(bytes);

//添加头信息
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
//输出到页面
        return response.writeWith(Mono.just(wrap));
    }
}
